﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;
using Enlightener.Engine.Structs__Enums___Interfaces;
using Enlightener.Engine.Utils;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Enlightener.Engine.Deobfuscator.Tasks._1_7
{
    public class ResourceDecryptor : DeobfuscationTask
    {
        public ResourceDecryptor(AssemblyDefinition asmDef)
            : base(asmDef)
        {
            RoutineDescription = "Decrypt and replace resources";
        }

        [DeobfuscationPhase(1, "Locate resource resolver")]
        public static bool Phase1()
        {
            var resolver =
                AsmDef.MainModule.Types.First(t => t.Name == "<Module>").Methods.FirstOrDefault(
                    m => m.Parameters.Count == 2 && m.Parameters[1].ParameterType.Name == "ResolveEventArgs");

            if(resolver == null)
            {
                PhaseError = new PhaseError
                                 {
                                     Level = PhaseError.ErrorLevel.Minor,
                                     Message = "No resource encryption?"
                                 };

                EmergencyCancel = true;
                return true;
            }

            var cctor = resolver.DeclaringType.Methods.First(m => m.IsConstructor && m.Name == ".cctor");

            MarkMember(cctor);
            MarkMember(resolver);

            Logger.VSLog("Found resolver method at " + resolver.Name);
            PhaseParam = resolver;

            return true;
        }

        /// <summary>
        /// Phaseparam = resolver
        /// </summary>
        [DeobfuscationPhase(2, "Decompress and load resource")]
        public static bool Phase2()
        {
            var resolver = PhaseParam as MethodDefinition;
            var resName = resolver.Body.Instructions.GetOperandAt<string>(OpCodes.Ldstr, 0);
            var modifier = resolver.Body.Instructions.GetOperandAt<int>(OpCodes.Ldc_I4, 0);

            if(resName == null)
            {
                PhaseError = new PhaseError
                                 {
                                     Level = PhaseError.ErrorLevel.Critical,
                                     Message = "Could not locate compressed resource"
                                 };
                return false;
            }

            MarkMember(AsmDef.MainModule.Resources.First(r => r.Name == resName));
            var streamRes = Assembly.LoadFile(DeobfuscatorContext.InPath).GetManifestResourceStream(resName);

            using(var br = new BinaryReader(new DeflateStream(streamRes, CompressionMode.Decompress)))
            {
                byte[] buffer = br.ReadBytes(br.ReadInt32());
                byte[] buffer2 = new byte[buffer.Length / 2];

                for (int i = 0; i < buffer.Length; i += 2)
                    buffer2[i / 2] = (byte)(((buffer[i + 1] ^ modifier) * modifier) + (buffer[i] ^ modifier));

                using(var br2 = new BinaryReader(new DeflateStream(new MemoryStream(buffer2), CompressionMode.Decompress)))
                    PhaseParam = Assembly.Load(br2.ReadBytes(br2.ReadInt32()));
            }

            return true;
        }

        /// <summary>
        /// PhaseParam = asm
        /// </summary>
        [DeobfuscationPhase(3, "Extract resources and replace old")]
        public static bool Phase4()
        {
            var asm = PhaseParam;

            foreach (var res in asm.GetManifestResourceNames())
            {
                var tmpStream = asm.GetManifestResourceStream(res);
                DeobfuscatorContext.ResStreams.Add(new ResEx {Name = res, ResStream = tmpStream});
                var buf = new byte[tmpStream.Length]; tmpStream.Read(buf, 0, buf.Length);
                
                AsmDef.MainModule.Resources.Add(new EmbeddedResource(res,
                                                 ManifestResourceAttributes.Private, buf));

                Logger.VSLog(string.Format(@"Injected original resource ""{0}""",
                                           AsmDef.MainModule.Resources[AsmDef.MainModule.Resources.Count -1].Name));
            }

            return true;
        }

        public static bool IsTargetResource(byte[] data, byte[] signature)
        {
            if (signature == null)
                return false;

            for(var i = 0;i < data.Length;i++)
            {
                if(data[i] == signature[0])
                {
                    var flag = true;

                    for(var x = 0; x < signature.Length;x++)
                    {
                        if (data[i + x] != signature[x])
                            flag = false;
                    }

                    if (flag)
                        return true;
                }
            }

            return false;
        }
        public static byte[] AlternateBytes(byte[] origData, byte alternator)
        {
            var tmpArr = origData;
            Array.Resize(ref tmpArr, origData.Length*2);

            for (var i = 1; i < tmpArr.Length; i += 2)
                tmpArr[i] = alternator;

            for (int i = 0, x = 0; i < tmpArr.Length; i += 2, x++)
                tmpArr[i] = origData[x];

            return tmpArr;
        }
    }
}
